﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  GetItemTask.cs
//    
// Abstract:
//
//  This module implements the GetItemTask class.
//
//--

using System;
using System.Diagnostics;

using Microsoft.OfficeCommunicationsServer.Applications.Common;

namespace Microsoft.EnhancedReminders.Scheduler
{
    /// <summary>
    /// The base class for tasks that get mailbox items via EWS.
    /// </summary>
    internal abstract class GetItemTask : ExchangeTask
    {
        // IDs of the items to be fetched
        protected BaseItemIdType[] _itemIds;

        // Item properties to be fetched
        protected readonly BasePathToElementType[] _properties;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this task</param>
        /// <param name="itemId">Mailbox ID of the item to be fetched</param>
        /// <param name="properties">Item properties to be fetched</param>
        protected GetItemTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType itemId, BasePathToElementType[] properties)
            : this(taskContext, client, impersonation, emailAddress, new BaseItemIdType[] { itemId }, properties)
        {
            Debug.Assert(itemId != null, "itemId != null");
        }

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this task</param>
        /// <param name="itemIds">Mailbox IDs of the items to be fetched</param>
        /// <param name="properties">Item properties to be fetched</param>
        protected GetItemTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, BaseItemIdType[] itemIds, BasePathToElementType[] properties)
            : base(taskContext, client, impersonation, emailAddress)
        {
            Debug.Assert(itemIds != null, "itemIds != null");
            Debug.Assert(properties != null, "properties != null");

            _itemIds = itemIds;
            _properties = properties;
        }

        /// <summary>
        /// Requests the mailbox item associated with this task from EWS.
        /// </summary>
        internal override void Begin()
        {
            Logger.WriteTraceVerbose(Context, "GetItem.Begin");

            GetItemType getItem = new GetItemType();
            getItem.ItemIds = _itemIds;
            getItem.ItemShape = new ItemResponseShapeType();
            getItem.ItemShape.BaseShape = DefaultShapeNamesType.IdOnly;
            getItem.ItemShape.AdditionalProperties = _properties;

            GetItemRequest getRequest = new GetItemRequest();
            getRequest.GetItem = getItem;
            getRequest.ExchangeImpersonation = _impersonation;
            getRequest.RequestServerVersion = ExchangeUsers.RequestServerVersion;

            try
            {
                _client.BeginGetItem(getRequest, OnGetItemCompleted, null);
            }
            catch (Exception ex)
            {
                HandleAccessError(ex, "BeginGetItem failed.");
            }
        }

        /// <summary>
        /// Returns the properties to request when fetching reminders from EWS.
        /// </summary>
        /// <remarks>
        /// This method is used to limit the properties that need to be returned when requesting an item from EWS. Requesting all 
        /// the properties puts unnecessary load on Exchange and remoting.
        /// </remarks>
        /// <returns>
        /// The array of properties.
        /// </returns>
        internal static BasePathToElementType[] GetRecurrenceProperties()
        {
            // Request the start time
            PathToUnindexedFieldType startDateFieldUri = new PathToUnindexedFieldType();
            startDateFieldUri.FieldURI = UnindexedFieldURIType.calendarStart;

            // Request the subject
            PathToUnindexedFieldType itemSubjectPath = new PathToUnindexedFieldType();
            itemSubjectPath.FieldURI = UnindexedFieldURIType.itemSubject;

            // Request the item class
            PathToUnindexedFieldType itemItemClassPath = new PathToUnindexedFieldType();
            itemItemClassPath.FieldURI = UnindexedFieldURIType.itemItemClass;

            // Request IsMeeting
            PathToUnindexedFieldType calendarIsMeetingPath = new PathToUnindexedFieldType();
            calendarIsMeetingPath.FieldURI = UnindexedFieldURIType.calendarIsMeeting;

            // Request the location
            PathToUnindexedFieldType calendarLocationPath = new PathToUnindexedFieldType();
            calendarLocationPath.FieldURI = UnindexedFieldURIType.calendarLocation;

            // Request the organizer
            PathToUnindexedFieldType calendarOrganizerPath = new PathToUnindexedFieldType();
            calendarOrganizerPath.FieldURI = UnindexedFieldURIType.calendarOrganizer;

            // Request the calendar type (e.g., RecurringMaster, Exception)
            PathToUnindexedFieldType calendarType = new PathToUnindexedFieldType();
            calendarType.FieldURI = UnindexedFieldURIType.calendarCalendarItemType;

            return new BasePathToElementType[] 
            { 
                startDateFieldUri, itemSubjectPath, itemItemClassPath, calendarIsMeetingPath,
                calendarLocationPath, calendarOrganizerPath, calendarType, ExchangeHelpers.GetExtendedPropertyPathForReminder(), 
                ExchangeHelpers.GetExtendedPropertyPathForReminderDelivered(),
                ExchangeHelpers.GetExtendedPropertyPathForExceptionDeletedFromExtendedProperties()
            };
        }

        /// <summary>
        /// Handles completion of the EWS request to fetch the mailbox item associated with this task.
        /// </summary>
        /// <param name="asyncResult">Status of the asynchronous operation</param>
        protected virtual void OnGetItemCompleted(IAsyncResult asyncResult)
        {
            Logger.WriteTraceVerbose(Context, "GetItemTask.OnGetItemCompleted");

            GetItemResponse getItemResponse = GetResponse<GetItemResponse>(asyncResult, _client.EndGetItem, "EndGetItem failed.");

            if (getItemResponse == null)
            {
                return;
            }

            ItemType item = null;

            ItemInfoResponseMessageType getItemResponseMessage = getItemResponse.GetItemResponse1.ResponseMessages.Items[0].AssertCast<ItemInfoResponseMessageType>();
            Debug.Assert(getItemResponseMessage != null, "getItemResponseMessage != null");

            if (getItemResponseMessage.ResponseClass == ResponseClassType.Success)
            {
                // Only expect one item because only one item was requested
                Debug.Assert(getItemResponseMessage.Items.Items.Length == 1, "getItemResponseMessage.Items.Items.Length == 1");
                item = getItemResponseMessage.Items.Items[0];
                Debug.Assert(item != null, "item != null");
                RaiseCompleted(new GetItemTaskCompletedEventArgs(_taskContext, item));
            }
            else
            {
                Logger.WriteTraceWarning(Context, "Failed to get item. Response code {0}", getItemResponseMessage.ResponseCode);
                RaiseCompleted(new TaskCompletedEventArgs(_taskContext,
                    new ExchangeTaskFailedException(Context, getItemResponseMessage.ResponseCode, "Failed to get item.")));
            }
        }
    }

    /// <summary>
    /// This handles the task of getting a calendar item via EWS.
    /// </summary>
    internal class GetCalendarItemTask : GetItemTask
    {
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this request</param>
        /// <param name="itemId">Mailbox ID of the item to be fetched</param>
        internal GetCalendarItemTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType itemId)
            : base(taskContext, client, impersonation, emailAddress, itemId, GetProperties())
        {
        }

        /// <summary>
        /// Returns the properties to request when fetching calendar items from EWS.
        /// </summary>
        /// <remarks>
        /// This method is used to limit the properties that need to be returned when requesting an item from EWS. Requesting all 
        /// the properties puts unnecessary load on Exchange and remoting.
        /// </remarks>
        /// <returns>
        /// The array of properties.
        /// </returns>
        private static BasePathToElementType[] GetProperties()
        {
            // Request the item class
            PathToUnindexedFieldType itemItemClassPath = new PathToUnindexedFieldType();
            itemItemClassPath.FieldURI = UnindexedFieldURIType.itemItemClass;

            // Request the calendar item start time
            PathToUnindexedFieldType calendarStartPath = new PathToUnindexedFieldType();
            calendarStartPath.FieldURI = UnindexedFieldURIType.calendarStart;

            return new BasePathToElementType[] { itemItemClassPath, calendarStartPath };
        }
    }

    /// <summary>
    /// This handles the task of getting a mailbox item with a reminder via EWS.
    /// </summary>
    internal class GetReminderItemTask : GetItemTask
    {
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this request</param>
        /// <param name="itemId">Mailbox ID of the item to be fetched</param>
        internal GetReminderItemTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType itemId)
            : base(taskContext, client, impersonation, emailAddress, itemId, GetProperties())
        {
        }

        /// <summary>
        /// Returns the properties to request when fetching reminders from EWS.
        /// </summary>
        /// <remarks>
        /// This method is used to limit the properties that need to be returned when requesting an item from EWS. Requesting all 
        /// the properties puts unnecessary load on Exchange and remoting.
        /// </remarks>
        /// <returns>
        /// The array of properties.
        /// </returns>
        private static BasePathToElementType[] GetProperties()
        {
            // Request the subject
            PathToUnindexedFieldType itemSubjectPath = new PathToUnindexedFieldType();
            itemSubjectPath.FieldURI = UnindexedFieldURIType.itemSubject;

            // Request the reminder due by time
            PathToUnindexedFieldType itemReminderDueByPath = new PathToUnindexedFieldType();
            itemReminderDueByPath.FieldURI = UnindexedFieldURIType.itemReminderDueBy;

            // Request the item class
            PathToUnindexedFieldType itemItemClassPath = new PathToUnindexedFieldType();
            itemItemClassPath.FieldURI = UnindexedFieldURIType.itemItemClass;

            // Request IsMeeting
            PathToUnindexedFieldType calendarIsMeetingPath = new PathToUnindexedFieldType();
            calendarIsMeetingPath.FieldURI = UnindexedFieldURIType.calendarIsMeeting;

            // Request the location
            PathToUnindexedFieldType calendarLocationPath = new PathToUnindexedFieldType();
            calendarLocationPath.FieldURI = UnindexedFieldURIType.calendarLocation;

            // Request the organizer
            PathToUnindexedFieldType calendarOrganizerPath = new PathToUnindexedFieldType();
            calendarOrganizerPath.FieldURI = UnindexedFieldURIType.calendarOrganizer;

            // Request the calendar item start time
            PathToUnindexedFieldType calendarStartPath = new PathToUnindexedFieldType();
            calendarStartPath.FieldURI = UnindexedFieldURIType.calendarStart;

            // Request the boolean field that tells us whether this is an occurrence of a recurring meeting
            PathToUnindexedFieldType calendarIsRecurrencePath = new PathToUnindexedFieldType();
            calendarIsRecurrencePath.FieldURI = UnindexedFieldURIType.calendarIsRecurring;

            // Request the calendar type (e.g., RecurringMaster, Exception)
            PathToUnindexedFieldType calendarType = new PathToUnindexedFieldType();
            calendarType.FieldURI = UnindexedFieldURIType.calendarCalendarItemType;

            // Requests the recurrence property
            PathToUnindexedFieldType calendarRecurrencePath = new PathToUnindexedFieldType();
            calendarRecurrencePath.FieldURI = UnindexedFieldURIType.calendarRecurrence;

            // Requests the modified occurrences property.
            PathToUnindexedFieldType modifiedOccurrencesPath = new PathToUnindexedFieldType();
            modifiedOccurrencesPath.FieldURI = UnindexedFieldURIType.calendarModifiedOccurrences;

            // Request the extended that conatins the reminder
            PathToExtendedFieldType reminderPropertyPath = ExchangeHelpers.GetExtendedPropertyPathForReminder();

            // Request the extended that conatins the reminder rule Id
            PathToExtendedFieldType reminderRuleIdPropertyPath = ExchangeHelpers.GetExtendedPropertyPathForReminderRuleId();

            // Request the extended that conatins the JoinNow rule Id
            PathToExtendedFieldType joinNowRuleIdPropertyPath = ExchangeHelpers.GetExtendedPropertyPathForJoinNowRuleId();

            // Request the extended property that conatins the conf info for a Live Meeting
            PathToExtendedFieldType joinInfoPropertyPath = ExchangeHelpers.GetExtendedPropertyPathForJoinInfo();

            // Request the extended property that contains the time a notification was delivered
            PathToExtendedFieldType reminderDeliveredPropertyPath = ExchangeHelpers.GetExtendedPropertyPathForReminderDelivered();

            return new BasePathToElementType[] { 
                itemSubjectPath, itemReminderDueByPath, itemItemClassPath, calendarIsMeetingPath, 
                calendarStartPath, calendarLocationPath, calendarOrganizerPath, calendarRecurrencePath, calendarType, calendarIsRecurrencePath, modifiedOccurrencesPath, reminderPropertyPath, 
                reminderRuleIdPropertyPath, joinNowRuleIdPropertyPath, joinInfoPropertyPath, reminderDeliveredPropertyPath };
        }
    }

    /// <summary>
    /// This handles the task of getting a mailbox item with an attachment via EWS.
    /// </summary>
    internal abstract class GetItemWithAttachmentTask : GetItemTask
    {
        protected ItemType _item;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this request</param>
        /// <param name="itemId">Mailbox ID of the item to be fetched</param>
        /// <param name="properties">Item properties to be fetched</param>
        protected GetItemWithAttachmentTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType itemId, BasePathToElementType[] properties)
            : base(taskContext, client, impersonation, emailAddress, itemId, properties)
        {
        }

        /// <summary>
        /// Get the specified attachment.
        /// </summary>
        /// <param name="attachmentId">The ID of the attachment to be fetched</param>
        protected void BeginGetAttachment(AttachmentIdType attachmentId)
        {
            Logger.WriteTraceVerbose(Context, "GetItemWithAttachmentTask.BeginGetAttachment");

            GetAttachmentType getAttachment = new GetAttachmentType();
            getAttachment.AttachmentShape = new AttachmentResponseShapeType();
            getAttachment.AttachmentIds = new RequestAttachmentIdType[] { attachmentId };

            GetAttachmentRequest getRequest = new GetAttachmentRequest();
            getRequest.GetAttachment = getAttachment;
            getRequest.ExchangeImpersonation = _impersonation;
            getRequest.RequestServerVersion = ExchangeUsers.RequestServerVersion;

            try
            {
                // Get the specified attachment via EWS
                _client.BeginGetAttachment(getRequest, OnGetAttachmentCompleted, null);
            }
            catch (Exception ex)
            {
                HandleAccessError(ex, "BeginGetAttachment failed.");
            }
        }

        /// <summary>
        /// Handles completion of the EWS request to fetch the mailbox item associated with this task.
        /// </summary>
        /// <param name="asyncResult">Status of the asynchronous operation</param>
        protected override void OnGetItemCompleted(IAsyncResult asyncResult)
        {
            Logger.WriteTraceVerbose(Context, "GetItemWithAttachmentTask.OnGetItemCompleted");

            GetItemResponse getItemResponse = GetResponse<GetItemResponse>(asyncResult, _client.EndGetItem, "EndGetItem failed.");

            if (getItemResponse == null)
            {
                return;
            }

            Exception error = null;

            ItemInfoResponseMessageType getItemResponseMessage = getItemResponse.GetItemResponse1.ResponseMessages.Items[0].AssertCast<ItemInfoResponseMessageType>();
            Debug.Assert(getItemResponseMessage != null, "getItemResponseMessage != null");

            if (getItemResponseMessage.ResponseClass == ResponseClassType.Success)
            {
                // Only expect one item because only one item was requested
                Debug.Assert(getItemResponseMessage.Items.Items.Length == 1, "getItemResponseMessage.Items.Items.Length == 1");
                _item = getItemResponseMessage.Items.Items[0];
                Debug.Assert(_item != null, "_item != null");

                if (_item.Attachments != null && _item.Attachments.Length == 1)
                {
                    // Get the voicemail attachment
                    BeginGetAttachment(_item.Attachments[0].AttachmentId);
                    return;
                }
                else
                {
                    // Voicemail messages and the storage item should only have one attachment
                    Logger.WriteTraceWarning(Context, "Unexpected number of attachments.");
                    error = new TaskFailedException(Context, "Unexpected number of attachments.");
                }
            }
            else
            {
                Logger.WriteTraceWarning(Context, "Failed to get item. Response code {0}", getItemResponseMessage.ResponseCode);
                error = new ExchangeTaskFailedException(Context, getItemResponseMessage.ResponseCode, "Failed to get item");
            }

            Debug.Assert(error != null, "error != null");

            RaiseCompleted(new TaskCompletedEventArgs(_taskContext, error));
        }

        /// <summary>
        /// Handles completion of the EWS request to fetch an attachemnt.
        /// </summary>
        /// <param name="asyncResult">Status of the asynchronous operation</param>
        private void OnGetAttachmentCompleted(IAsyncResult asyncResult)
        {
            Debug.Assert(_item != null, "_item != null");

            Logger.WriteTraceVerbose(Context, "GetItemWithAttachmentTask.OnGetAttachmentCompleted");

            GetAttachmentResponse getAttachmentResponse = GetResponse<GetAttachmentResponse>(asyncResult, _client.EndGetAttachment, "EndGetAttachment failed.");

            if (getAttachmentResponse == null)
            {
                return;
            }

            FileAttachmentType fileAttachment = null;

            AttachmentInfoResponseMessageType getAttachmentResponseMessage = getAttachmentResponse.GetAttachmentResponse1.ResponseMessages.Items[0].AssertCast<AttachmentInfoResponseMessageType>();
            Debug.Assert(getAttachmentResponseMessage != null, "getAttachmentResponseMessage != null");

            if (getAttachmentResponseMessage.ResponseClass == ResponseClassType.Success)
            {
                fileAttachment = getAttachmentResponseMessage.Attachments[0] as FileAttachmentType;
            }

            if (fileAttachment == null || fileAttachment.Content == null)
            {
                Logger.WriteTraceWarning(Context, "Failed to get attachment. Response code {0}", getAttachmentResponseMessage.ResponseCode);
                RaiseCompleted(new TaskCompletedEventArgs(_taskContext,
                    new ExchangeTaskFailedException(Context, getAttachmentResponseMessage.ResponseCode, "Failed to get attachment")));
            }
            else
            {
                RaiseCompleted(new GetItemWithAttachmentTaskCompletedEventArgs(_taskContext, _item, fileAttachment));
            }
        }
    }

    /// <summary>
    /// This handles the task of getting a voicemail mailbox item (including audio attachment) via EWS.
    /// </summary>
    internal class GetVoicemailItemTask : GetItemWithAttachmentTask
    {
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this request</param>
        /// <param name="itemId">Mailbox ID of the item to be fetched</param>
        internal GetVoicemailItemTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType itemId)
            : base(taskContext, client, impersonation, emailAddress, itemId, GetProperties())
        {
        }

        /// <summary>
        /// Returns the properties to request when fetching voicemails from EWS.
        /// </summary>
        /// <remarks>
        /// This method is used to limit the properties that need to be returned when requesting an item from EWS. Requesting all 
        /// the properties puts unnecessary load on Exchange and remoting.
        /// </remarks>
        /// <returns>
        /// The array of properties.
        /// </returns>
        private static BasePathToElementType[] GetProperties()
        {
            // Request the attachments
            PathToUnindexedFieldType itemAttachmentsPath = new PathToUnindexedFieldType();
            itemAttachmentsPath.FieldURI = UnindexedFieldURIType.itemAttachments;

            // Request the date-time received
            PathToUnindexedFieldType itemDateTimeReceivedPath = new PathToUnindexedFieldType();
            itemDateTimeReceivedPath.FieldURI = UnindexedFieldURIType.itemDateTimeReceived;

            // Request the item class
            PathToUnindexedFieldType itemItemClassPath = new PathToUnindexedFieldType();
            itemItemClassPath.FieldURI = UnindexedFieldURIType.itemItemClass;

            // Request the extended property that conatins the caller ID
            PathToExtendedFieldType callerIdPropertyPath = ExchangeHelpers.GetExtendedStringProperty(ExchangeHelpers.CallerIdPropertyTag);

            // Request the extended property that conatins the PR_SENT_REPRESENTING_NAME property
            PathToExtendedFieldType fromNamePropertyPath = ExchangeHelpers.GetExtendedStringProperty(ExchangeHelpers.FromNamePropertyTag);

            return new BasePathToElementType[] { itemAttachmentsPath, itemDateTimeReceivedPath, itemItemClassPath, 
                callerIdPropertyPath, fromNamePropertyPath };
        }
    }

    /// <summary>
    /// This handles the task of getting a storage item (including attachment) via EWS.
    /// </summary>
    internal class GetStorageItemTask : GetItemWithAttachmentTask
    {
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this request</param>
        /// <param name="itemId">Mailbox ID of the item to be fetched</param>
        internal GetStorageItemTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType itemId)
            : base(taskContext, client, impersonation, emailAddress, itemId, GetProperties())
        {
        }

        /// <summary>
        /// Returns the properties to request when fetching storage items from EWS.
        /// </summary>
        /// <remarks>
        /// This method is used to limit the properties that need to be returned when requesting an item from EWS. Requesting all 
        /// the properties puts unnecessary load on Exchange and remoting.
        /// </remarks>
        /// <returns>
        /// The array of properties.
        /// </returns>
        private static BasePathToElementType[] GetProperties()
        {
            // Request the subject
            PathToUnindexedFieldType itemSubjectPath = new PathToUnindexedFieldType();
            itemSubjectPath.FieldURI = UnindexedFieldURIType.itemSubject;

            // Request the attachments
            PathToUnindexedFieldType itemAttachmentsPath = new PathToUnindexedFieldType();
            itemAttachmentsPath.FieldURI = UnindexedFieldURIType.itemAttachments;

            return new BasePathToElementType[] { itemSubjectPath, itemAttachmentsPath };
        }
    }

    /// <summary>
    /// This handles getting the next occurence of a recurring meeting
    /// </summary>
    internal class GetRecurringItemTask : GetItemTask
    {
        // the current occurrence index
        private int _currentIndex = 1;

        // The reminder item associated with the recurring master
        private ReminderItem _reminderItem;

        /// <summary>
        /// The last delivery time for the recurring master, if available
        /// </summary>
        protected long _lastDelivery;

        /// <summary>
        /// Whether or not the recurring master has a last delivery time
        /// </summary>
        protected bool _hasLastDelivery;

        /// <summary>
        /// The ItemId for the recurring master item.
        /// </summary>
        protected ItemIdType _masterItemId;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this task</param>
        /// <param name="masterItemId">Mailbox ID of the recurring master</param>
        /// <param name="reminderItem">The reminder item for the recurring master</param>
        /// <param name="lastDelivery">The last delivery time of the recurring master</param>
        internal GetRecurringItemTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType masterItemId, string reminderItem, string lastDelivery)
            : base(taskContext, client, impersonation, emailAddress, GetOccurrenceItems(1, masterItemId), GetRecurrenceProperties())            
        {
            Debug.Assert(masterItemId != null, "masterItemId != null");

            _masterItemId = masterItemId;

            if (reminderItem != null)
            {
                _reminderItem = EnhancedRemindersHelpers.DeserializeReminderItem(taskContext.Context, reminderItem);
            }

            _hasLastDelivery = long.TryParse(lastDelivery, out _lastDelivery);
        }

        private static BaseItemIdType[] GetOccurrenceItems(int startIndex, ItemIdType masterItemId)
        {
            OccurrenceItemIdType[] occurrenceItems = new OccurrenceItemIdType[Constants.OccurrencesToFetch];

            for (int index = 0; index < occurrenceItems.Length; index++)
            {
                OccurrenceItemIdType occurrence = new OccurrenceItemIdType();
                occurrence.ChangeKey = masterItemId.ChangeKey;
                occurrence.RecurringMasterId = masterItemId.Id;
                occurrence.InstanceIndex = startIndex;

                occurrenceItems[index] = occurrence;
                startIndex++;
            }

            return occurrenceItems;
        }

        /// <summary>
        /// Handles completion of the EWS request to fetch the mailbox item associated with this task.
        /// </summary>
        /// <param name="asyncResult">Status of the asynchronous operation</param>
        protected override void OnGetItemCompleted(IAsyncResult asyncResult)
        {
            Logger.WriteTraceVerbose(Context, "GetRecurringItemTask.OnGetItemCompleted");

            GetItemResponse getItemResponse = GetResponse<GetItemResponse>(asyncResult, _client.EndGetItem, "EndGetItem failed.");

            if (getItemResponse == null)
            {
                return;
            }

            ItemInfoResponseMessageType getItemResponseMessage = getItemResponse.GetItemResponse1.ResponseMessages.Items[0].AssertCast<ItemInfoResponseMessageType>();
            Debug.Assert(getItemResponseMessage != null, "getItemResponseMessage != null");

            for (int index = 0; index < getItemResponse.GetItemResponse1.ResponseMessages.Items.Length; index++)
            {
                if (getItemResponse.GetItemResponse1.ResponseMessages.Items[index].ResponseCode == ResponseCodeType.NoError)
                {
                    CalendarItemType calendarItem = getItemResponse.GetItemResponse1.ResponseMessages.Items[index].AssertCast<ItemInfoResponseMessageType>().Items.Items[0].AssertCast<CalendarItemType>();
                    ReminderItem currentReminderItem = _reminderItem;
                    bool reminderOnExceptionWasDeleted = ExchangeHelpers.GetReminderOnExceptionDeletedFromExtendedProperties(calendarItem);

                    if (calendarItem.CalendarItemType1 == CalendarItemTypeType.Exception)
                    {
                        if (reminderOnExceptionWasDeleted)
                        {
                            currentReminderItem = null;
                        }
                        else
                        {
                            string reminderItem = ExchangeHelpers.GetReminderFromExtendedProperties(calendarItem);

                            if (reminderItem != null)
                            {
                                currentReminderItem = EnhancedRemindersHelpers.DeserializeReminderItem(Context, reminderItem);
                            }
                        }
                    }

                    // The reminder item may be null because the user is running an older version of the Outlook Add-in
                    // that is not compatible with this version of the scheduler. We'll log a warning when we attempt to
                    // deserialize it, but we still need to do this check here.
                    if (currentReminderItem == null)
                    {
                        if (reminderOnExceptionWasDeleted)
                        {
                            Logger.WriteTraceInformation(_taskContext.Context, "Reminder on exception to recurring calendar item was deleted by the user");
                        }
                        else
                        {
                            Logger.WriteTraceWarning(_taskContext.Context, "Unable to deserialize reminder item for recurring meeting. Reminder will be ignored, but the user should be instructed to update their version of the Enhanced Reminder Add-In and update their reminders.");

                            // raise a "No error" error so that we continue to monitor the user's mailbox.
                            RaiseCompleted(new TaskCompletedEventArgs(_taskContext,
                                new ExchangeTaskFailedException(Context, ResponseCodeType.NoError, "Failed to get item.")));
                            return;
                        }
                    }
                    else
                    {
                        DateTime reminderDueTime = DateTime.MinValue;
                        DateTime joinNowDueTime = DateTime.MinValue;

                        if (currentReminderItem.IsNotificationEnabled)
                        {
                            reminderDueTime = currentReminderItem.GetDueTime(calendarItem.Start, false);
                        }
                        if (currentReminderItem.IsJoinNowEnabled)
                        {
                            joinNowDueTime = currentReminderItem.GetDueTime(calendarItem.Start, true);
                        }

                        // Raise the completed event if either the reminder due time or join now due time is in the future.
                        if (currentReminderItem.IsNotificationEnabled && (reminderDueTime.Ticks > (DateTime.UtcNow - SchedulerService._sendCutoffTimePeriod).Ticks && (!_hasLastDelivery || reminderDueTime.Ticks > _lastDelivery)) ||
                            currentReminderItem.IsJoinNowEnabled && (joinNowDueTime.Ticks > (DateTime.UtcNow - SchedulerService._sendCutoffTimePeriod).Ticks && (!_hasLastDelivery || joinNowDueTime.Ticks > _lastDelivery)))
                        {
                            RaiseCompleted(new GetNextRecurringItemTaskCompletedEventArgs(_taskContext, calendarItem, currentReminderItem, _masterItemId));
                            return;
                        }
                    }
                }
                else
                {
                    if (getItemResponse.GetItemResponse1.ResponseMessages.Items[index].ResponseCode == ResponseCodeType.ErrorCalendarOccurrenceIsDeletedFromRecurrence)
                    {
                        Logger.WriteTraceVerbose(_taskContext.Context, "Hit deleted occurrence, moving to next occurence.");
                    }
                    else if (getItemResponse.GetItemResponse1.ResponseMessages.Items[index].ResponseCode == ResponseCodeType.ErrorCalendarOccurrenceIndexIsOutOfRecurrenceRange)
                    {
                        Logger.WriteTraceVerbose(_taskContext.Context, "Failed to find any matching occurrences: navigated beyond the recurrence range.");

                        // if we navigate beyond the recurrence range, raise the failed event and return.
                        RaiseCompleted(new TaskCompletedEventArgs(_taskContext,
                            new ExchangeTaskFailedException(Context, getItemResponseMessage.ResponseCode, "Failed to get item.")));
                        return;
                    }
                }
            }

            // Otherwise, re-call begin and we'll fetch the next batch of occurrence items.
            _itemIds = GetOccurrenceItems(_currentIndex, _masterItemId);
            _currentIndex += _itemIds.Length;
            Begin();
        }
    }

    /// <summary>
    /// Implementation of GetItemTask that fetches the modified occurrences of a recurring meeting
    /// that does not have a reminder associated with it so that we can determine whether or not
    /// any of the exceptions have a reminder associated with them.
    /// </summary>
    internal class GetNextModifiedOccurrenceTask : GetItemTask
    {
        /// <summary>
        /// The last delivery time for the recurring master, if available
        /// </summary>
        protected long _lastDelivery;

        /// <summary>
        /// Whether or not the recurring master has a last delivery time
        /// </summary>
        protected bool _hasLastDelivery;

        /// <summary>
        /// The ItemId for the recurring master item.
        /// </summary>
        protected ItemIdType _masterItemId;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this task</param>
        /// <param name="itemId">Mailbox ID of the item to be fetched</param>
        /// <param name="lastDelivery">The last delivery time of the recurring master</param>
        /// <param name="items">The ItemIds to fetch.</param>
        internal GetNextModifiedOccurrenceTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType itemId, string lastDelivery, ItemIdType[] items)
            : base(taskContext, client, impersonation, emailAddress, items, GetRecurrenceProperties())
        {
            Debug.Assert(itemId != null, "itemId != null");

            _masterItemId = itemId;
            _hasLastDelivery = long.TryParse(lastDelivery, out _lastDelivery);
        }

        /// <summary>
        /// Handles completion of the EWS request to fetch the mailbox item associated with this task.
        /// </summary>
        /// <param name="asyncResult">Status of the asynchronous operation</param>
        protected override void OnGetItemCompleted(IAsyncResult asyncResult)
        {
            Logger.WriteTraceVerbose(Context, "GetNextModifiedOccurrenceTask.OnGetItemCompleted");

            GetItemResponse getItemResponse = GetResponse<GetItemResponse>(asyncResult, _client.EndGetItem, "EndGetItem failed.");

            if (getItemResponse == null)
            {
                return;
            }

            ItemInfoResponseMessageType getItemResponseMessage = getItemResponse.GetItemResponse1.ResponseMessages.Items[0].AssertCast<ItemInfoResponseMessageType>();
            Debug.Assert(getItemResponseMessage != null, "getItemResponseMessage != null");

            for (int index = 0; index < getItemResponse.GetItemResponse1.ResponseMessages.Items.Length; index++)
            {
                if (getItemResponse.GetItemResponse1.ResponseMessages.Items[index].ResponseCode == ResponseCodeType.NoError)
                {
                    CalendarItemType calendarItem = getItemResponse.GetItemResponse1.ResponseMessages.Items[index].AssertCast<ItemInfoResponseMessageType>().Items.Items[0].AssertCast<CalendarItemType>();
                    string reminderItemString = ExchangeHelpers.GetReminderFromExtendedProperties(calendarItem);

                    if (reminderItemString != null)
                    {
                        ReminderItem reminderItem = EnhancedRemindersHelpers.DeserializeReminderItem(Context, reminderItemString);

                        // The reminder item may be null because the user is running an older version of the Outlook Add-in
                        // that is not compatible with this version of the scheduler. We'll log a warning when we attempt to
                        // deserialize it, but we still need to do this check here.
                        if (reminderItem == null)
                        {
                            Logger.WriteTraceWarning(_taskContext.Context, "Unable to deserialize reminder item for recurring meeting. Reminder will be ignored, but the user should be instructed to update their version of the Enhanced Reminder Add-In and update their reminders.");

                            // raise a "No error" error so that we continue to monitor the user's mailbox.
                            RaiseCompleted(new TaskCompletedEventArgs(_taskContext,
                                new ExchangeTaskFailedException(Context, ResponseCodeType.NoError, "Failed to get item.")));
                            return;
                        }
                        else
                        {
                            DateTime reminderDueTime = DateTime.MinValue;
                            DateTime joinNowDueTime = DateTime.MinValue;

                            if (reminderItem.IsNotificationEnabled)
                            {
                                reminderDueTime = reminderItem.GetDueTime(calendarItem.Start, false);
                            }
                            if (reminderItem.IsJoinNowEnabled)
                            {
                                joinNowDueTime = reminderItem.GetDueTime(calendarItem.Start, true);
                            }

                            // Raise the completed event if either the reminder due time or join now due time is in the future.
                            if (reminderItem.IsNotificationEnabled && (reminderDueTime.Ticks > (DateTime.UtcNow - SchedulerService._sendCutoffTimePeriod).Ticks && (!_hasLastDelivery || reminderDueTime.Ticks > _lastDelivery)) ||
                                reminderItem.IsJoinNowEnabled && (joinNowDueTime.Ticks > (DateTime.UtcNow - SchedulerService._sendCutoffTimePeriod).Ticks && (!_hasLastDelivery || joinNowDueTime.Ticks > _lastDelivery)))
                            {
                                RaiseCompleted(new GetNextRecurringItemTaskCompletedEventArgs(_taskContext, calendarItem, reminderItem, _masterItemId));
                                return;
                            }
                        }
                    }
                }
                else
                {
                    Logger.WriteTraceVerbose(_taskContext.Context, "Hit missing occurrence. {0}", getItemResponse.GetItemResponse1.ResponseMessages.Items[index].ResponseCode);
                }
            }

            Logger.WriteTraceWarning(Context, "Failed to get item. Response code {0}", getItemResponseMessage.ResponseCode);
            RaiseCompleted(new TaskCompletedEventArgs(_taskContext,
                new ExchangeTaskFailedException(Context, getItemResponseMessage.ResponseCode, "Failed to get item.")));
        }
    }
}


